package cn.imatu.framework.data.mate.fieldconvert;

import cn.imatu.framework.data.mate.fieldconvert.inter.ICustomFieldConvertAnnotGetter;
import cn.imatu.framework.data.mate.fieldconvert.inter.IFieldValueConvert;
import cn.imatu.framework.data.mate.fieldconvert.model.AnnotInfo;
import cn.imatu.framework.data.mate.fieldconvert.model.FieldDefine;
import cn.imatu.framework.data.mate.fieldconvert.model.TargetClassCache;
import cn.imatu.framework.exception.BizException;
import cn.imatu.framework.tool.core.ReflectUtils;
import cn.imatu.framework.tool.core.StringUtils;
import cn.imatu.framework.tool.core.reflection.DefaultReflectorFactory;
import cn.imatu.framework.tool.core.reflection.MetaObject;
import cn.imatu.framework.tool.core.reflection.factory.DefaultObjectFactory;
import cn.imatu.framework.tool.core.reflection.wrapper.DefaultObjectWrapperFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 转换字段类扫描
 * @author shenguangyang
 */
@Slf4j
@Component
@SuppressWarnings(value = {"unchecked", "rawtypes"})
public class ScanConvertFieldHandler implements ApplicationContextAware {
    private final SetTargetFieldValueManager fieldValueManager;
    private IFieldValueConvert fieldValueConvert;
    @Resource
    private Map<String, ICustomFieldConvertAnnotGetter> fieldConvertAnnotGetterMap;
    private static final Map<Class<?>, TargetClassCache> CACHE = new ConcurrentHashMap<>();
    private static final DefaultObjectFactory defaultObjectFactory = new DefaultObjectFactory();
    private static final DefaultObjectWrapperFactory defaultObjectWrapperFactory = new DefaultObjectWrapperFactory();
    private static final DefaultReflectorFactory defaultReflectorFactory = new DefaultReflectorFactory();

    public ScanConvertFieldHandler(SetTargetFieldValueManager fieldValueManager) {
        this.fieldValueManager = fieldValueManager;
    }

    @PostConstruct
    public void init() {

    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, IFieldValueConvert> map = applicationContext.getBeansOfType(IFieldValueConvert.class);
        if (map.size() > 2) {
            throw new RuntimeException("there can only be one " + IFieldValueConvert.class.getSimpleName() + " subclass in a module");
        }
        if (CollectionUtils.isEmpty(map)) {
            throw new BizException("没有实现 {} 字段翻译子类", IFieldValueConvert.class.getName());
        }
        for (Map.Entry<String, IFieldValueConvert> entry : map.entrySet()) {
            fieldValueConvert = entry.getValue();
            log.warn("use [{}] complete field convert", entry.getValue().getClass().getName());
        }
    }

    private TargetClassCache createTargetClassCache(Object targetObj) {
        if (Objects.isNull(targetObj)) {
            return null;
        }
        Class<?> targetClass = targetObj.getClass();
        TargetClassCache targetClassCache = CACHE.get(targetClass);
        if (Objects.nonNull(targetClassCache)) {
            return targetClassCache;
        }
        synchronized (targetObj.getClass().getName().intern()) {
            targetClassCache = CACHE.get(targetClass);
            if (Objects.nonNull(targetClassCache)) {
                return targetClassCache;
            }

            TargetClassCache newTargetClassCache = new TargetClassCache();
            newTargetClassCache.setClazz(targetClass);
            List<Field> allFields = ReflectUtils.getFieldsWithSuper(targetObj);

            fieldConvertAnnotGetterMap.forEach((key, value) -> {
                Class<? extends Annotation> fieldAnnot = (Class<? extends Annotation>) ((ParameterizedType) AopUtils.getTargetClass(value)
                    .getGenericInterfaces()[0])
                    .getActualTypeArguments()[0];

                allFields.forEach(f -> {
                    f.setAccessible(true);
                    Annotation annot = f.getAnnotation(fieldAnnot);
                    if (Objects.isNull(annot)) {
                        return;
                    }

                    AnnotInfo annotInfo = value.annotInfo(annot);
                    annotInfo.setCustomAnnot(annot);
                    annotInfo.setSourceField(f.getName());
                    annotInfo.setTargetField(StringUtils.firstNonEmpty(annotInfo.getTargetField(), f.getName()));

                    FieldDefine fieldDefine = new FieldDefine();
                    fieldDefine.setField(f);
                    fieldDefine.setAnnot(annotInfo);
                    try {
                        Field targetField = f.getDeclaringClass().getDeclaredField(annotInfo.getTargetField());
                        targetField.setAccessible(true);
                        fieldDefine.setTargetField(targetField);
                        newTargetClassCache.getFieldList().add(fieldDefine);
                    } catch (Exception e1) {
                        log.error("error: ", e1);
                        throw new BizException("字段翻译异常");
                    }
                });

            });

            CACHE.put(targetClass, newTargetClassCache);

            return newTargetClassCache;
        }
    }

    public void handleField(Object obj, boolean isReturn) {
        Map<Object, Class<?>> map = new ConcurrentHashMap<>();
        handleField(obj, map, isReturn);
    }

    /**
     * 完成收集字段
     */
    private void handleField(Object obj, Map<Object, Class<?>> map, boolean isReturn) {
        if (Objects.isNull(obj)) {
            return;
        }

        if (obj instanceof Collection) {
            handleCollectionField((Collection<Object>) obj, map, isReturn);
        } else if (obj.getClass().isArray()) {
            handleArrayField(obj, map, isReturn);
        } else if (obj instanceof Map) {
            handleMapField((Map<Object, Object>) obj, map, isReturn);
        } else if (!isJavaClass(obj.getClass())) {
            handleSingleField(obj, map, isReturn);
        }
    }

    private void handleMapField(Map<Object, Object> objMap, Map<Object, Class<?>> map, boolean isReturn) {
        if (Objects.isNull(objMap)) {
            return;
        }
        objMap.forEach((key, value) -> handleField(value, map, isReturn));
    }

    /**
     * 完成对象中字段绑定
     */
    private void handleObjectField(Object obj, Map<Object, Class<?>> map, boolean isReturn) {
        if (Objects.isNull(obj)) {
            return;
        }
        List<Field> fields = ReflectUtils.getFieldsWithSuper(obj);
        for (Field field : fields) {
            try {
                int modifiers = field.getModifiers();
                if (Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)
                        || Modifier.isInterface(modifiers) || Modifier.isAbstract(modifiers)) {
                    continue;
                }
                field.setAccessible(true);
                Object o = field.get(obj);
                handleField(o, map, isReturn);
            } catch (Exception e) {
                log.error("error: ", e);
            }
        }
    }

    /**
     * 完成单字段绑定, eg:
     * class Test {
     *  String name;
     *  Long age;
     * }
     */
    private void handleSingleField(Object obj, Map<Object, Class<?>> map, boolean isReturn) {
        TargetClassCache targetClassCache = CACHE.get(obj.getClass());
        if (Objects.isNull(targetClassCache)) {
            targetClassCache = createTargetClassCache(obj);
            if (Objects.isNull(targetClassCache)) {
                return;
            }
        }

        int hashCode = System.identityHashCode(obj);
        if (map.containsKey(hashCode)) {
            return;
        }
        map.put(hashCode, obj.getClass());
        handleObjectField(obj, map, isReturn);

        MetaObject metaObject = MetaObject.forObject(obj, defaultObjectFactory, defaultObjectWrapperFactory, defaultReflectorFactory);
        processTargetField(obj, metaObject, targetClassCache, isReturn);

    }

    @SuppressWarnings("Duplicates")
    private void handleArrayField(Object obj, Map<Object, Class<?>> map, boolean isReturn) {
        int hashCode = System.identityHashCode(obj);
        if (Objects.isNull(obj) || map.containsKey(hashCode)) {
            return;
        }
        map.put(hashCode, obj.getClass());
        int len = Array.getLength(obj);
        if (len == 0) {
            return;
        }
        for (int i = 0; i < len; i++) {
            handleField(Array.get(obj, i), map, isReturn);
        }
    }

    /**
     * 对集合中的每个对象做字段绑定处理
     */
    private void handleCollectionField(Collection<Object> objs, Map<Object, Class<?>> map, boolean isReturn) {
        int hashCode = System.identityHashCode(objs);
        if (CollectionUtils.isEmpty(objs) || map.containsKey(hashCode)) {
            return;
        }
        map.put(hashCode, objs.getClass());
        objs.stream().filter(Objects::nonNull).findFirst()
                .ifPresent(obj -> objs.parallelStream().forEach(o -> handleField(o, map, isReturn)));
    }

    /**
     * 调度数据绑定接口
     *
     * @param metaObject     对象元数据
     * @param targetClassCache 目标类信息缓存
     */
    private void processTargetField(Object obj, MetaObject metaObject,
                                    TargetClassCache targetClassCache, boolean isReturn) {
        if (Objects.isNull(obj)) {
            return;
        }
        List<FieldDefine> fieldList = targetClassCache.getFieldList();
        fieldList.stream().parallel().forEach(fieldDefine -> {
            try {
                fieldValueManager.setTargetValue(metaObject, obj, fieldValueConvert, fieldDefine, isReturn);
            } catch (Exception e) {
                log.error("映射字段失败 -> error: ", e);
            }
        });
    }

    private static boolean isJavaClass(Class<?> clz) {
        return clz.getClassLoader() == null;
    }
}
